/*
绘制三角形基本流程：
1、初始化：初始化GLFW和GLAD
2、数据处理：生成和绑定VBO和VAO，设置属性指针
3、着色器：顶点和片段着色器
4、渲染
5、善后工作
*/
#include "OpenGLCase1.h"

const char* vertex_shader_soure =
"#version 330 core\n"
"layout(location = 0) in vec3 aPos;\n" // 位置变量的属性位置为0
"void main()\n"
"{\n"
"	gl_Position = vec4(aPos, 1.0);\n"
"}\n\0";

const char* fragment_shader_soure =
"#version 330 core\n"
"out vec4 FragColor;\n" // 输出的颜色向量
"void main()\n"
"{\n"
"	FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";

void runCase1() {

	// 1、初始化
	// 1) 初始化GLFW
	// 2）创建窗口
	// 3) 初始化GLAD
	// 4) 创建视口

	glfwInit(); // 初始化GLFW
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // OpenGL版本为3.3，主板本号设置为3
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // OpenGL版本为3.3，次版本号设置为3
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 使用核心模式（无需向后兼容性）
	//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); //如果使用MAC OS X系统,需要加上这一行
	glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); // 不可以改变窗口大小

	int width = 800, height = 600; // 窗口框高
	auto window = glfwCreateWindow(width, height, "case1：draw triangle", nullptr, nullptr); // 创建窗口（宽，高，窗口名称，是否使用全屏模式，共享上下文的窗口）

	// 窗口创建失败输出错误信息
	if (window == nullptr) {
		printf("create window error!");
		glfwTerminate();
		return;
	}

	// 上下文的概念：opengl本身是一个大的状态机，这里我们称opengl的状态为上下文，因此设置过后我们就是用当前的上下文来进行渲染，
	// 这里我理解的是将opengl的渲染状态绑定到对应的window，因为window其实就是一个上下文环境（目前的window是一个指针类似创建窗口的句柄上下文的概念），这样opengl的渲染就可以作用于window窗口了。
	glfwMakeContextCurrent(window); // 将窗口的上下文设置成为当前线程的主上下文

	// 初始化GLAD，加载OpenGL函数指针地址的函数。
	// GLAD是用来管理OpenGL的函数指针的，所以任何OpenGL的操作都需要通过GLAD来完成，这里需要先初始化GLAD。
	// 这里我的理解是，OpenGL并不是一个标准库，而是接口规范，实现是有各个硬件厂家来完成的，那么如何做到不同平台不同硬件加载不同的具体实现函数是个问题。
	// 所以提出了GLAD这样一层来实现动态加载处理。
	// 下面两句话的意思，有点像glad要加载一个OpenGL,需要一个程序上下问地址，而GLFW提供了一个程序地址
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
		printf("glad init load opengl error!");
		//system("pause");
		return;
	}

	glViewport(0, 0, width, height); // 指定当前视口尺寸（前两个参数指定左下角原点位置，宽，高）


	// 2、数据处理
	// 1) 顶点数据定义
	// 2) 生成绑定VBO和VAO
	// 3) 设置属性指针
	// 4) 解绑

	// 三角形的顶点数据，这里的顶点数据都是规范化的坐标数据，所以必须在（-1,1）之间，形状如图： △
	const float triangle[] = {
		// ----位置-----
		-0.5f,  0.0f, 0.0f, // 左下
		 0.5f,  0.0f, 0.0f, // 右下
		 0.0f,  1.0f, 0.0f  // 正上
	};
	
	// 我们有了顶点数据，接下来就需要将顶点数据发送到GPU中去处理
	// 生成并绑定立方体的VAO和VBO
	// 使用VAO的原因是我们使用了核心模式，它要求我们需要使用VAO，其次使用VAO的好处是我们在渲染的时候只需要调用一次VAO就可以了。之间的数据都对应存在了VAO，不在调用VBO
	GLuint vertex_array_object; // VAO
	glGenVertexArrays(1, &vertex_array_object); // 生成VAO
	glBindVertexArray(vertex_array_object); // 绑定并激活VAO

	// 使用VBO的好处，是可以一次性发送大片数据到gpu中，而不是一个个传输。
	GLuint vertex_buffer_object; // VBO
	glGenBuffers(1, &vertex_buffer_object); // 生成VBO
	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object); // 绑定并激活VBO

	// 将顶点数据绑定至当前默认的缓冲中
	glBufferData(GL_ARRAY_BUFFER, sizeof(triangle), triangle, GL_STATIC_DRAW); // GL_STATIC_DRAW 表示我们的三角形位置数据不会被改变
	
	// 我理解的上面的代码的意义是，由于数据的渲染是需要在gpu中进行，这里使用opengl在gpu上分配并开辟一些缓存buffer，并得到引用id或句柄之类的，
	// 开辟生成好缓存空间后，在通过 glBufferData 填充数据到gpu中，这里没有使用得到的引用ID和句柄，应该是bind操作时带有类似激活的操作相当于选择了指定的buffer那么后面的操作都是针对这个buffer。
	// 从VAO、VBO和EBO的关系，我们得知，其实绘制渲染的基础数据应该是VBO数据，而VAO和EBO应该都是更好的批量或缓存使用VBO数据而提出的，那么也就是不是必须的。


	// 发送到GPU后我们还需要告诉GPU如何解释这些数据
	// 设置顶点属性指针
	// 使用这个函数来告诉GPU如何解释顶点数据，比如：
	// 第一个参数：0：顶点着色器的位置值或通道值，一般的显卡会有提供固定数量的渲染管道，
	// 第二个参数：3：表示顶点数据是个3分量的向量，
	// 第三个参数：GL_FLOAT：表示顶点数据的类型
	// 第四个参数：GL_FALSE：表示我们是否希望数据被标准化,就是映射到（0，1）之间
	// 第五个参数：3 * sizeof(float)：步长，表示连续顶点属性之间的间隔，即描述一个顶点需要多长的数据
	// 第六个参数：(void*)0：数据的偏移量，这里我们从其实开始读取，这里是0，并且参数类型限制，我们需要强制的类型转换
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); 
	glEnableVertexAttribArray(0); // 表示我们要开启0这个通道，默认状态下事关闭的所以需要开启一下。


	// 解绑VAO和VBO
	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	// 为什么要使用完要解绑，是为了防止后续对VAO和VBO的修改影响当前渲染结果，其次是为了代码更加规范，所以有绑定在使用完了一定需要解绑。
	// 我的理解是，绑定操作像是选择或激活操作，上述过程中，相当于激活了buffer区或者说选择了某个buffer如果操作完成，我们不吧这个buffer去从操作台上拿走，那么后面的操作可能弄脏这个区域


	// 生成并编译着色器
	// 顶点着色器
	int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertex_shader, 1, &vertex_shader_soure, NULL);
	glCompileShader(vertex_shader);
	int success;
	char info_log[512];
	// 检查着色器编译是否成功，如果失败打印错误信息
	glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertex_shader, 512, NULL, info_log);
		printf("error::shader::vertex::%s", info_log);
	}

	// 片段着色器
	int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragment_shader, 1, &fragment_shader_soure, NULL);
	glCompileShader(fragment_shader);
	// 检查着色器编译是否成功，如果失败打印错误信息
	glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(fragment_shader, 512, NULL, info_log);
		printf("error::shader::fragment::%s", info_log);
	}

	// 链接顶点与片段着色器至一个着色器程序
	int shader_program = glCreateProgram();
	glAttachShader(shader_program, vertex_shader);
	glAttachShader(shader_program, fragment_shader);
	glLinkProgram(shader_program);
	// 检查程序连接是否成功，如果失败打印错误信息
	glGetShaderiv(shader_program, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(shader_program, 512, NULL, info_log);
		printf("error::shader::fragment::%s", info_log);
	}

	// 删除着色器程序
	glDeleteShader(vertex_shader);
	glDeleteShader(fragment_shader);


	// 渲染
	while (!glfwWindowShouldClose(window)) {
		// 清空颜色缓冲
		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		// 使用着色器程序
		glUseProgram(shader_program);

		// 绘制三角形
		glBindVertexArray(vertex_array_object); // 绑定VAO
		glDrawArrays(GL_TRIANGLES, 0, 3); // 绘制三角形
		glBindVertexArray(0); //接触绑定

		// 交换缓冲并检查是否有触发事件（鼠标、键盘等）
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	// 善后工作
	// 删除VAO,VBO
	glDeleteVertexArrays(1, &vertex_array_object);
	glDeleteBuffers(1, &vertex_buffer_object);

	// 清理所有资源并正确退出程序
	glfwTerminate();

	//GLFWwindow* window = glfwCreateWindow(width, height, "TestOpenGL", NULL, NULL);

	//if (!window) {
	//	printf("create window error!");
	//	glfwTerminate();
	//	//system("pause");
	//	return ;
	//}

	//glfwMakeContextCurrent(window);

	//if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
	//	printf("glad init load opengl error!");
	//	//system("pause");
	//	return ;
	//}

	//GLuint VAO, VBO, EBO;

//	while (!glfwWindowShouldClose(window)) {
//		glfwPollEvents();
//
//		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
//		glClear(GL_COLOR_BUFFER_BIT);
///*
//		glBindBuffer(1, VAO);
//		glBindVertexArray(VAO);*/
//
//		glfwSwapBuffers(window);
//	}

}